(function (window) {
  var utils = {
    // color:rgb或rgba格式
    // opacity: 透明度
    calculateColor: function (color, opacity) {
      if (color.indexOf('#') === 0) {
        var color16 = color.slice(1);
        var r = parseInt(color16.slice(0, 2), 16);
        var g = parseInt(color16.slice(2, 4), 16);
        var b = parseInt(color16.slice(4), 16);
        return 'rgba(' + r + ',' + g + ',' + b + ',' + opacity + ')';
      } else if (/^rgb\(/.test(color)) {
        return color.replace(/rgb/, 'rgba').replace(')', ",") +
          opacity + ')';
      } else {
        return color.split(',').splice(0, 3).join(',') +
          opacity + ')';
      }
    }
  };
  var arrayUtils = {
    forEach: function (arr, cb, scope) {
      if (typeof Array.prototype.forEach === 'function') {
        arr.forEach(cb, scope);
      } else {
        for (var i = 0, len = arr.length; i < len; i++) {
          cb.apply(scope, [arr[i], i, arr]);
        }
      }
    },
    map: function (arr, cb, scope) {
      if (typeof Array.prototype.map === 'function') {
        return arr.map(cb, scope);
      } else {
        var mapped = [];
        for (var i = 0, len = arr.length; i < len; i++) {
          mapped[i] = cb.apply(scope, [arr[i], i, arr]);
        }
        return mapped;
      }
    }
  };

  var Marker = (function () {
    var M = function (options) {
      this.x = options.x;
      this.y = options.y;
      this.rotation = options.rotation;
      this.style = options.style;
      this.color = options.color;
      this.size = options.size;
      this.borderWidth = options.borderWidth;
      this.borderColor = options.borderColor;
    };

    M.prototype.draw = function (context) {
      context.save();
      context.translate(this.x, this.y);
      context.rotate(this.rotation);

      context.lineWidth = this.borderWidth || 0;
      context.strokeStyle = this.borderColor || '#000';
      context.fillStyle = this.color || '#000';
      // 目前先只支持圆
      context.beginPath();
      if (this.style === 'circle') {
        context.arc(0, 0, this.size, 0, Math.PI * 2, false);
      } else if (this.style === 'arrow') {
        context.moveTo(-this.size, -this.size);
        context.lineTo(this.size, 0);
        context.lineTo(-this.size, this.size);
        context.lineTo(-this.size / 4, 0);
        context.lineTo(-this.size, -this.size);
      }
      context.closePath();
      context.stroke();
      context.fill();
      context.restore();
    };

    return M;
  })();

  var Arc = (function () {
    var A = function (options) {
      var startX = options.startX,
        startY = options.startY,
        endX = options.endX,
        endY = options.endY;
      //两点之间的圆有多个，通过两点及半径便可以定出两个圆，根据需要选取其中一个圆
      var L = Math.sqrt(Math.pow(startX - endX, 2) + Math.pow(startY - endY, 2));
      var m = (startX + endX) / 2; // 横轴中点
      var n = (startY + endY) / 2; // 纵轴中点
      var factor = 1.5;

      var centerX = (startY - endY) * factor + m;
      var centerY = (endX - startX) * factor + n;

      var radius = Math.sqrt(Math.pow(L / 2, 2) + Math.pow(L * factor, 2));
      var startAngle = Math.atan2(startY - centerY, startX - centerX);
      var endAngle = Math.atan2(endY - centerY, endX - centerX);

      // this.L = L;
      this.startX = startX;
      this.startY = startY;
      this.endX = endX;
      this.endY = endY;
      this.centerX = centerX;
      this.centerY = centerY;
      this.startAngle = startAngle;
      this.endAngle = endAngle;
      this.startLabel = options && options.labels && options.labels[0],
        this.endLabel = options && options.labels && options.labels[1],
        this.radius = radius;
      this.lineWidth = options.width || 1;
      this.strokeStyle = options.color || '#000';
      this.label = options.label;
      this.font = options.font;
      this.shadowBlur = options.shadowBlur;
    };

    A.prototype.draw = function (context) {
      context.save();
      context.lineWidth = this.lineWidth;
      context.strokeStyle = this.strokeStyle;
      context.shadowColor = this.strokeStyle;
      context.shadowBlur = this.shadowBlur || 2;

      context.beginPath();
      context.arc(this.centerX, this.centerY, this.radius, this.startAngle, this.endAngle, false);
      context.stroke();
      context.restore();

      context.save();
      context.fillStyle = this.strokeStyle;
      if (this.label) {
        context.font = this.font;
        if (this.startLabel) {
          var x = this.startX - 15
          var y = this.startY + 5
          context.fillText(this.startLabel, x, y);
        }
        if (this.endLabel) {
          var x = this.endX - 15;
          var y = this.endY - 5;
          context.fillText(this.endLabel, x, y);
        }
      }
      context.restore();
    };

    return A;
  })();

  var Pulse = (function () {
    function P(options) {
      this.x = options.x;
      this.y = options.y;
      this.maxRadius = options.radius;
      this.color = options.color;
      this.shadowBlur = 5;
      this.lineWidth = options.borderWidth;
      this.r = 0;
      this.factor = 2 / options.radius;
    };

    P.prototype.draw = function (context) {
      // var vr = (this.maxRadius - this.r) * this.factor;
      var vr = 0.5;
      this.r += vr;
      // this.shadowBlur = Math.floor(this.r);

      context.save();
      context.translate(this.x, this.y);
      var strokeColor = this.color;
      strokeColor = utils.calculateColor(strokeColor, 1 - this.r / this.maxRadius);
      context.strokeStyle = strokeColor;
      context.shadowBlur = this.shadowBlur;
      context.shadowColor = strokeColor;
      context.lineWidth = this.lineWidth;
      context.beginPath();
      context.arc(0, 0, this.r, 0, Math.PI * 2, false);
      context.stroke();
      context.restore();

      if (Math.abs(this.maxRadius - this.r) < 0.8) {
        this.r = 0;
      }
    }

    return P;
  })();

  var Spark = (function () {
    var S = function (options) {
      var startX = options.startX,
        startY = options.startY,
        endX = options.endX,
        endY = options.endY;

      //两点之间的圆有多个，通过两点及半径便可以定出两个圆，根据需要选取其中一个圆
      var L = Math.sqrt(Math.pow(startX - endX, 2) + Math.pow(startY - endY, 2));
      var m = (startX + endX) / 2; // 横轴中点
      var n = (startY + endY) / 2; // 纵轴中点
      var factor = 1.5;

      var centerX = (startY - endY) * factor + m;
      var centerY = (endX - startX) * factor + n;

      var radius = Math.sqrt(Math.pow(L / 2, 2) + Math.pow(L * factor, 2));
      var startAngle = Math.atan2(startY - centerY, startX - centerX);
      var endAngle = Math.atan2(endY - centerY, endX - centerX);

      // 保证Spark的弧度不超过Math.PI
      if (startAngle * endAngle < 0) {
        if (startAngle < 0) {
          startAngle += Math.PI * 2;
          endAngle += Math.PI * 2;
        } else {
          endAngle += Math.PI * 2;
        }
      }

      this.tailPointsCount = 50; // 拖尾点数
      this.centerX = centerX;
      this.centerY = centerY;
      this.startAngle = startAngle;
      this.endAngle = endAngle;
      this.radius = radius;
      this.lineWidth = options.width || 1;
      this.strokeStyle = options.color || '#fff';
      this.factor = 2 / this.radius;
      this.deltaAngle = (80 / Math.min(this.radius, 400)) / this.tailPointsCount;
      this.trailAngle = this.startAngle;
      this.arcAngle = this.startAngle;

      this.animateBlur = true;

      this.marker = new Marker({
        x: 50,
        y: 80,
        rotation: 50 * Math.PI / 180,
        style: 'arrow',
        color: 'rgb(255, 255, 255)',
        size: 3,
        borderWidth: 0,
        borderColor: this.strokeStyle
      });
    };

    S.prototype.drawArc = function (context, strokeColor, lineWidth, startAngle, endAngle) {
      context.save();
      context.lineWidth = lineWidth;
      // context.lineWidth = 5;
      context.strokeStyle = strokeColor;
      context.shadowColor = this.strokeStyle;
      // context.shadowBlur = 5;
      context.lineCap = "round";
      context.beginPath();
      context.arc(this.centerX, this.centerY, this.radius, startAngle, endAngle, false);
      context.stroke();
      context.restore();
    };

    S.prototype.draw = function (context) {
      var endAngle = this.endAngle;
      // 匀速
      var angle = this.trailAngle + this.factor;
      var strokeColor = this.strokeStyle;
      if (this.animateBlur) {
        this.arcAngle = angle;
      }
      this.trailAngle = angle;
      strokeColor = utils.calculateColor(strokeColor, 0.1);

      this.drawArc(context, strokeColor, this.lineWidth, this.startAngle, this.arcAngle);

      // 拖尾效果
      var count = this.tailPointsCount;
      for (var i = 0; i < count; i++) {
        var arcColor = utils.calculateColor(this.strokeStyle, 0.3 - 0.3 / count * i);
        var tailLineWidth = 5;
        if (this.trailAngle - this.deltaAngle * i > this.startAngle) {
          this.drawArc(context, arcColor,
            tailLineWidth - tailLineWidth / count * i,
            this.trailAngle - this.deltaAngle * i,
            this.trailAngle
          );
        }
      }

      context.save();
      context.translate(this.centerX, this.centerY);
      this.marker.x = Math.cos(this.trailAngle) * this.radius;
      this.marker.y = Math.sin(this.trailAngle) * this.radius;
      this.marker.rotation = this.trailAngle + Math.PI / 2;
      this.marker.draw(context);
      context.restore();

      if ((endAngle - this.trailAngle) * 180 / Math.PI < 0.5) {
        this.trailAngle = this.startAngle;
        this.animateBlur = false;
      }
    };

    return S;
  })();


  var Migration = (function () {
    var M = function (options) {
      this.data = options.data;
      this.store = {
        arcs: [],
        markers: [],
        pulses: [],
        sparks: []
      };
      this.playAnimation = true;
      this.started = false;
      this.context = options.context;
      this.style = options.style;
      this.init();
    };

    M.prototype.init = function () {
      this.updateData(this.data);
    };
    /*
     * Shape 必须拥有draw方法
    */
    M.prototype.add = function (Shape) {

    };
    M.prototype.remove = function () {

    };
    M.prototype.clear = function () {
      this.store = {
        arcs: [],
        markers: [],
        pulses: [],
        sparks: []
      };
      // 更新状态
      this.playAnimation = true;
      this.started = false;
      // 清除绘画实例，如果没有这个方法，多次调用start，相当于存在多个动画队列同时进行
      window.cancelAnimationFrame(this.requestAnimationId);
    };
    /*
     * 更新数据
    */
    M.prototype.updateData = function (data) {
      if (!data || data.length === 0) {
        return;
      }
      this.clear();
      this.data = data;
      if (this.data && this.data.length > 0) {
        arrayUtils.forEach(this.data, function (element) {
          var arc = new Arc({
            startX: element.from[0],
            startY: element.from[1],
            endX: element.to[0],
            endY: element.to[1],
            labels: element.labels,
            label: this.style.arc.label,
            font: this.style.arc.font,
            width: this.style.arc.width,
            color: element.color
          });
          var marker = new Marker({
            x: element.to[0],
            y: element.to[1],
            rotation: arc.endAngle + Math.PI / 2,
            style: 'arrow',
            color: element.color,
            size: 4,
            borderWidth: 0,
            borderColor: element.color
          });
          var pulse = new Pulse({
            x: element.to[0],
            y: element.to[1],
            radius: this.style.pulse.radius,
            color: element.color,
            borderWidth: this.style.pulse.borderWidth
          });
          var spark = new Spark({
            startX: element.from[0],
            startY: element.from[1],
            endX: element.to[0],
            endY: element.to[1],
            width: 15,
            color: element.color
          });

          this.store.arcs.push(arc);
          this.store.markers.push(marker);
          this.store.pulses.push(pulse);
          this.store.sparks.push(spark);
        }, this);
      }
    };
    /*
    */
    M.prototype.start = function (canvas) {
      var that = this;
      if (!this.started) {
        (function drawFrame() {
          that.requestAnimationId = window.requestAnimationFrame(drawFrame, canvas);

          if (that.playAnimation) {
            canvas.width += 1;
            canvas.width -= 1;
            for (var p in that.store) {
              var shapes = that.store[p];
              for (var i = 0, len = shapes.length; i < len; i++) {
                shapes[i].draw(that.context);
              }
            }
          }
        })();
        this.started = true;
      }
    };
    M.prototype.play = function () {
      this.playAnimation = true;
    };
    M.prototype.pause = function () {
      this.playAnimation = false;
    };
    return M;
  })();

  L.MigrationLayer = L.Class.extend({
    options: {
      map: {},
      data: {},
      pulseRadius: 25,
      pulseBorderWidth: 3,
      arcWidth: 1,
      arcLabel: true,
      arcLabelFont: '15px sans-serif',
      Marker: {},
      Spark: {}

    },
    _setOptions: function (obj, options) {
      if (!obj.hasOwnProperty('options')) {
        obj.options = obj.options ? L.Util.create(obj.options) : {};
      }
      for (var i in options) {
        obj.options[i] = options[i];
      }
      return obj.options;
    },
    initialize: function (options) {
      this._setOptions(this, options);
      this._map = this.options.map || {};
      this._data = this.options.data || {};
      this._style = {
        pulse: {
          radius: this.options.pulseRadius,
          borderWidth: this.options.pulseBorderWidth
        },
        arc: {
          width: this.options.arcWidth,
          label: this.options.arcLabel,
          font: this.options.arcLabelFont
        }
      } || {};
      this._show = true;
      this._init();
    },
    _init: function () {
      var container = L.DomUtil.create('div', 'leaflet-ODLayer-container');
      container.style.position = 'absolute';
      container.style.width = this._map.getSize().x + "px";
      container.style.height = this._map.getSize().y + "px";

      this.container = container;

      this.canvas = document.createElement('canvas');
      this.context = this.canvas.getContext('2d');
      container.appendChild(this.canvas);
      this._map.getPanes().overlayPane.appendChild(container);
      //this._resize();
      if (!this.migration) {
        var data = this._convertData();
        this.migration = new Migration({
          data: data,
          context: this.context,
          style: this._style
        });
        //this._draw();
        //this._bindMapEvents();
      }
    },
    _resize: function () {
      var bounds = this._map.getBounds();
      var topleft = bounds.getNorthWest();
      var topLeftscreen = this._map.latLngToContainerPoint(topleft);
      //当地图缩放或者平移到整个地图的范围小于外层容器的范围的时候，要对this.container进行上下平移操作，反之则回归原位
      if (topLeftscreen.y > 0) {
        this.container.style.top = -topLeftscreen.y + 'px';
      } else {
        this.container.style.top = '0px';
      }

      var containerStyle = window.getComputedStyle(this._map.getContainer());
      this.canvas.setAttribute('width', parseInt(containerStyle.width, 10));
      this.canvas.setAttribute('height', parseInt(containerStyle.height, 10));
    },
    _convertData: function () {
      var bounds = this._map.getBounds();

      if (this._data && bounds) {
        var data = arrayUtils.map(this._data, function (d) {

          var fromPixel = this._map.latLngToContainerPoint(new L.LatLng(d.from[1], d.from[0]));
          var toPixel = this._map.latLngToContainerPoint(new L.LatLng(d.to[1], d.to[0]));
          return {
            from: [fromPixel.x, fromPixel.y],
            to: [toPixel.x, toPixel.y],
            labels: d.labels,
            value: d.value,
            color: d.color
          }
        }, this);

        return data;
      }
    },
    _bindMapEvents: function () {
      var that = this;
      // window.onresize = function () {
      //     that._resize();
      // };
      // this._map.on('movestart ', function () {
      //     that.migration.pause();
      // });
      this._map.on('moveend', function () {
        that.migration.play();
        that._draw();
      });
      this._map.on('zoomstart ', function () { that.container.style.display = 'none' });
      this._map.on('zoomend', function () {
        if (that._show) {
          that.container.style.display = ''
          that._draw();
        }
      });
    },
    _draw: function () {
      var bounds = this._map.getBounds();
      if (bounds && this.migration.playAnimation) {
        this._resize();
        this._transform();
        var data = this._convertData();
        this.migration.updateData(data);
        this.migration.start(this.canvas);
      }
    },
    _transform: function () {
      var bounds = this._map.getBounds();
      var topLeft = this._map.latLngToLayerPoint(bounds.getNorthWest());
      L.DomUtil.setPosition(this.container, topLeft);
    },
    addTo: function () {
      this._bindMapEvents();
      var bounds = this._map.getBounds();
      if (bounds && this.migration.playAnimation) {
        this._resize();
        this._transform();

        var data = this._convertData();
        this.migration.updateData(data);
        this.migration.start(this.canvas);
      }
    },
    setData: function (data) {
      this._data = data;
      this._draw();
    },
    hide: function () {
      this.container.style.display = 'none';
      this._show = false;
    },
    show: function () {
      this.container.style.display = '';
      this._show = true;
    },
    play: function () {
      this.migration.play();
    },
    pause: function () {
      this.migration.pause();
    },
    destroy: function () {
      this.migration.clear();
      //移除dom
      this.container.parentNode.removeChild(this.container);
      //移除事件监听
      this._map.clearAllEventListeners();
      this.mapHandles = [];
    }

  });
  L.migrationLayer = function (options) {
    return new L.MigrationLayer(options)
  }
})(window)
